import * as fs from 'fs';
import axios from 'axios';
import * as qs from 'querystring';
import * as crypto from 'crypto';

type Params = { [key: string]: any }

/**
 * 公钥证书方式模式
 * 详情：https://docs.open.alipay.com/291/106118
 * */
export class AlipayUtil {
    private readonly app_id: string = '2019012063126188';

    /**
     * 公钥证书方式模式
     * 详情：https://docs.open.alipay.com/291/106118
     * */
    /**由（应用公钥证书：appCertPublicKey.crt）生成*/
    private readonly app_cert_sn: string = "1afb67c28bb0ae7e6946c8b1c0f71df5";
    /**由（支付宝根证书：alipayRootCert.crt）生成*/
    private readonly alipay_root_cert_sn: string = "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6";
    /**由（支付宝公钥证书：alipayCertPublicKey_RSA2.crt）生成，用于异步验签*/
    private readonly alipay_cert_sn: string = "63220d6f8f22de19c834e956f76d5bb2";
    private readonly privateKey: string = fs.readFileSync('config/private.pem', 'ascii');

    constructor() {
        let { alipay_cert_sn, privateKey } = this
        if (privateKey.indexOf('BEGIN') === -1) {
            this.privateKey = `-----BEGIN RSA PRIVATE KEY-----\n${privateKey}\n-----END RSA PRIVATE KEY-----`
        }
        else {
            this.privateKey = privateKey.toString();
        }

        if (alipay_cert_sn.indexOf('BEGIN') === -1) {
            this.alipay_cert_sn = `-----BEGIN PUBLIC KEY-----\n${alipay_cert_sn}\n-----END PUBLIC KEY-----`
        }
        else {
            this.alipay_cert_sn = alipay_cert_sn.toString();
        }
    }

	/**
	 * 生成签名
	 * @param params {string} 待签参数
	 * @return signed string
	 */
    public static rsaSign(params: string | Params, privateKey: string): string {
        if (typeof params === "string")
            return crypto.createSign("RSA-SHA256")
                .update(params)
                .sign(privateKey, 'base64');
        let data = AlipayUtil.formatter(params);
        let formatStr = AlipayUtil.stringify(data)
        return AlipayUtil.rsaSign(formatStr, privateKey);
    }

    public static timestamp() {
        let t = new Date()
        let year = t.getFullYear().toString();
        var month = (t.getMonth() + 1).toString();
        if (month.length < 2) month = "0" + month;
        var date = t.getDate().toString();
        if (date.length < 2) date = "0" + date;
        var hours = t.getHours().toString();
        if (hours.length < 2) hours = "0" + hours;
        var mintues = t.getMinutes().toString();
        if (mintues.length < 2) mintues = "0" + mintues;
        var seconds = t.getSeconds().toString();
        if (seconds.length < 2) seconds = "0" + seconds;
        return `${year}-${month}-${date} ${hours}:${mintues}:${seconds}`
    }

    private static stringify(data: Params): string {
        return qs.unescape(qs.stringify(data))
    }

	/**
	 * 筛选、排序、拼接请求参数
	 * @param params {JSON}
	 * @return 待签名字符串
	 */
    private static formatter(params: Params): Params {
        const sortedParams = {};
        Object.keys(params).sort().map(key => {
            if (typeof params[key] === 'object') {
                params[key] = JSON.stringify(params[key]);
            }
            sortedParams[key] = params[key];
        });
        return sortedParams;
    }
	/**
	 * 验签
	 * 详情：https://docs.open.alipay.com/200/106120
	 * @param data {JSON} 被验证签名obj
	 * @return {Boolean}
	 */
    public rsaCheck(data: Params): boolean {
        const sign: string = decodeURIComponent(data.sign);

        delete data.sign;//除去sign参数
        delete data.sign_type;//除去sign_type参数
        data = AlipayUtil.formatter(data);
        let content = AlipayUtil.stringify(data)

        return crypto
            .createVerify("RSA-SHA256")
            .update(content)
            .verify(this.alipay_cert_sn, sign, 'base64');
    }

    public exec(method: string, params: Params): Promise<AlipayResponse> {
        let data = Object.assign({}, params, {
            app_id: this.app_id,
            method: method,
            charset: "utf-8",
            sign_type: "RSA2",
            app_cert_sn: this.app_cert_sn,
            alipay_root_cert_sn: this.alipay_root_cert_sn,
            timestamp: AlipayUtil.timestamp(),
            version: "1.0"
        });

        data.sign = AlipayUtil.rsaSign(data, this.privateKey);
        // data = AlipayUtil.formatter(data)
        let content = qs.stringify(data)
        return axios.post('https://openapi.alipay.com/gateway.do', content, {
            headers: {
                'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
            }
        }).then(res => {
            let data: AlipayHttpResponse = res.data;
            let ret: AlipayResponse = data[method.replace(/\./g, '_') + '_response'];
            if (ret.code != '10000') throw ret;
            return ret;
        });
    }

    getAmount() {
        return this.exec('alipay.fund.account.query', {
            biz_content: {
                alipay_user_id: '2088431257368210',
                account_product_code: 'DING_ACCOUNT',
            }
        })
    }

    pay(trade_id: string, telphone: string, name: string, price: number): Promise<AlipayFundTransUniTransferResponse> {
        return this.exec(`alipay.fund.trans.uni.transfer`, {
            biz_content: {
                out_biz_no: trade_id, // 64	商户端的唯一订单号，对于同一笔转账请求，商户需保证该订单号唯一
                trans_amount: price, // [0.1,1,0000,0000] 订单总金额，单位为元，精确到小数点后两位，STD_RED_PACKET产品取值范围[0.01,100000000]
                // 收发现金红包固定为: STD_RED_PACKET
                // 单笔无密转账到支付宝账户固定为: TRANS_ACCOUNT_NO_PWD
                // 单笔无密转账到银行卡固定为: TRANS_BANKCARD_NO_PWD
                product_code: "TRANS_ACCOUNT_NO_PWD",
                // order_title: "提现测试", // 64
                payee_info: {
                    identity: telphone,
                    // 参与方的标识类型，目前支持如下类型：
                    // 1、 ALIPAY_USER_ID 支付宝的会员ID
                    // 2、 ALIPAY_LOGON_ID 支付宝登录号，支持邮箱和手机号格式
                    identity_type: 'ALIPAY_LOGON_ID',
                    name: name,
                },
                // 描述特定的业务场景，可传的参数如下：
                // PERSONAL_COLLECTION: C2C现金红包-领红包；
                // DIRECT_TRANSFER：B2C现金红包、单笔无密转账到支付宝/银行卡
                biz_scene: 'DIRECT_TRANSFER',
                remark: '红包测试' // 200
            }
        }) as any;
    }
}

export interface AlipayHttpResponse {
    [key: string]: any;
    alipay_cert_sn: string;
    sign: string;
}

export interface AlipayResponse {
    [key: string]: any;
    code: string;
    msg: string;
    sub_code?: string,
    sub_msg?: string
}

export interface AlipayFundTransUniTransferResponse extends AlipayResponse {
    order_id: string;
    out_biz_no: string;
    pay_fund_order_id: string;
    status: string;
    trans_date: string;
}

export function test() {
    let alipay = new AlipayUtil();
    let out = alipay.rsaCheck({
        alipay_fund_trans_uni_transfer_response:
        {
            code: '10000',
            msg: 'Success',
            order_id: '20200223110070000006210053918577',
            out_biz_no: '1',
            pay_fund_order_id: '20200223110070001506210054471688',
            status: 'SUCCESS',
            trans_date: '2020-02-23 10:56:42'
        },
        alipay_cert_sn: '63220d6f8f22de19c834e956f76d5bb2',
        sign: 'mYoYmD6qfPeoRxu8lwB/akXiQpJTIn3nkl1mSworo1TToTHABcBdbM82lqzRtor1mdYaIq3tEA5SCwPgi0gZZ1s76MZz1Xd7YGIc0+WLEXUaFtRF0RUI0NlE+HiWaEY7B6WbXqCxrlWS18kfjIPaLPLl8c7BQatAaOoRxC/ycGtmxlyekC8kQOLZEr9S6Ju51MPShz4tsuiMGh5GR3sUbgCcIXDF+lZLrMnxfc89xPshDcM+g9kzdivgtyeboH8YNFsdirIMiIrhKC2SEL5oe5GYVJFt/SfybKgA7u1V2uVEfat4j7n0XfLxpny/Hb3CoL5rpCCWXHDPz71wcAhIUg=='
    })
    console.log(out)
    // return alipay.pay()
}

if (require.main === module) {
    async function main() {
        let data = await test()
        console.log(data)
    }

    main().then(function() {
        console.log("end");
    }).catch(function(err) {
        console.log(err);
        console.log("err end");
    });
}